package download;

import java.io.*;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLConnection;
import java.nio.ByteBuffer;
import java.nio.channels.Channels;
import java.nio.channels.FileChannel;
import java.nio.channels.ReadableByteChannel;
import java.nio.file.*;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicLong;
/**
 * @Description 下载任务
 * @Author 冷彭磊
 * @Data 2025/2/27 9:00
 */
public class DownloadTask2 {
    private static final int BUFFER_SIZE = 1024 * 8;
    private volatile int lastReportedPercent = -1;
    //记录进度
    private volatile int progress = -1;
    //下载链接
    private final String fileUrl;
    //下载保存地址
    private final String localFilePath;
    //线程数量
    private final int threadCount;
    //下载文件的总大小
    private long fileSize;
    //下载进度
    private final Map<Integer, Long> progressMap = new ConcurrentHashMap<>();
    //线程池
    private ExecutorService executor;
    private  DownloadListener listener;
    //总下载进度
    private final AtomicLong downloadedBytes = new AtomicLong();
    //MD5值
    private String md5;
    //是否支持断点续传
    private boolean breakpointContinuation;

    public DownloadTask2(String fileUrl, String localFilePath, int threadCount,DownloadListener listener,String md5,boolean breakpointContinuation) {
        this.listener = listener;
        this.fileUrl = fileUrl;
        this.md5 = md5;
        this.breakpointContinuation = breakpointContinuation;
        this.localFilePath = localFilePath;
        this.threadCount = threadCount;
    }



    public void download(ExecutorService executor2) throws IOException, InterruptedException, ExecutionException {
        this.executor = executor2;
        listener.onBreakpointContinuation(localFilePath,breakpointContinuation);

        //如果文件不存在，创建
        File file = new File(localFilePath);
        if (!file.exists()) {
            file.createNewFile();
        }


        // 获取文件总大小
        HttpURLConnection conn = (HttpURLConnection) new URL(fileUrl).openConnection();
        conn.setRequestMethod("HEAD");
        fileSize = conn.getContentLengthLong();
        conn.disconnect();

        //监听开始下载
        listener.onStart(fileUrl,localFilePath,md5);

        if (breakpointContinuation){
            //获取下载进度
            loadProgress();
        }

        //如果支持断点续传就执行多线程，否则单线程
        if (breakpointContinuation) {
            for (int i = 0; i < threadCount; i++) {
                executor.submit(new DownloadWorker(i, listener));
            }
        } else {
            executor.submit(new singleThread(listener));
        }



    }
    //单线程下载
    private class singleThread implements Runnable{

        DownloadListener listener;
        private final AtomicLong newDownloadedBytes = new AtomicLong();
        public singleThread(DownloadListener listener) {
            this.listener = listener;
        }

        @Override
        public void run() {
            try (RandomAccessFile file = new RandomAccessFile(localFilePath, "rw");
                 FileChannel channel = file.getChannel()) {
                URLConnection conn = new URL(fileUrl).openConnection();
                try (ReadableByteChannel inChannel = Channels.newChannel(conn.getInputStream())){
                    ByteBuffer buffer = ByteBuffer.allocate(BUFFER_SIZE);
                    long position = 0;

                    while (inChannel.read(buffer) != -1) {
                        buffer.flip();
                        int bytesWritten = channel.write(buffer, position);
                        position += bytesWritten;
                        newDownloadedBytes.addAndGet(bytesWritten);
                        downloadedBytes.addAndGet(bytesWritten);

                        listener.onProgress(getProgressFilePath(),downloadedBytes.get(),fileSize);
                        buffer.clear();
                    }
                }

            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    //多线程下载
    private class DownloadWorker implements Runnable {
        private final int threadId;
        private long startPos;
        private long endPos;
        DownloadListener listener;
        private final AtomicLong newDownloadedBytes = new AtomicLong();
        public DownloadWorker(int threadId,DownloadListener listener) {
            this.threadId = threadId;
            this.listener = listener;
            long blockSize = fileSize / threadCount;
            startPos = threadId * blockSize;
            endPos = (threadId == threadCount - 1) ? fileSize - 1 : startPos + blockSize - 1;

//            System.out.println("恢复数据前" + localFilePath+ "线程" + threadId+ ":" + startPos + "-" + endPos) ;

            // 断点续传
            if (progressMap.containsKey(threadId) ) {
                long saved = progressMap.get(threadId);
                startPos += saved;
                newDownloadedBytes.set(saved);
            }

//            System.out.println("恢复数据后"+ localFilePath+ "线程" + threadId+ ":" + startPos + "-" + endPos) ;


        }

        @Override
        public void run() {
            int maxRetryCount = 3;
            int retryCount = 0;
            while (retryCount < maxRetryCount){
                try (RandomAccessFile file = new RandomAccessFile(localFilePath, "rw");
                     FileChannel channel = file.getChannel()) {
                    URLConnection conn = new URL(fileUrl).openConnection();
                    conn.setRequestProperty("Range", "bytes=" + startPos + "-" + endPos);
                    InputStream inputStream = null;
                    try {
                        inputStream = conn.getInputStream();
                    } catch (IOException e) {
                        retryCount++;
                        System.out.println("链接请求失败,开始重试");
                        if (retryCount >= maxRetryCount){
                            e.printStackTrace();
                        }
                    }
                    try (ReadableByteChannel inChannel = Channels.newChannel(inputStream)){
                        ByteBuffer buffer = ByteBuffer.allocate(BUFFER_SIZE);
                        long position = startPos;

                        while (inChannel.read(buffer) != -1) {
                            buffer.flip();
                            int bytesWritten = channel.write(buffer, position);
                            position += bytesWritten;
                            newDownloadedBytes.addAndGet(bytesWritten);
                            downloadedBytes.addAndGet(bytesWritten);

                            saveProgress(threadId, newDownloadedBytes.get());
                            listener.onProgress(getProgressFilePath(), downloadedBytes.get(),fileSize);
                            buffer.clear();
                        }
                    }
                    break;
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }

        }
    }

    //写入进度
    private  synchronized void saveProgress(int threadId, long progress) {
        progressMap.put(threadId, progress);
        //使用临时文件避免清空原文件内容
        File tempFile = new File(getProgressFilePath() + ".tmp");

        try (BufferedWriter writer = new BufferedWriter(new FileWriter(tempFile))) {
            for (Map.Entry<Integer, Long> entry : progressMap.entrySet()) {
                writer.write(entry.getKey() + ":" + entry.getValue());
                writer.newLine();
            }
        } catch (IOException e) {
            System.out.println("写入临时进度文件失败");
            e.printStackTrace();
            return;
        }

        try {
            //如果移动操作已成功完成，则源文件被删除，目标文件完整存在。
            //如果操作未完成，源文件保持原样，目标文件不存在。
            Files.move(tempFile.toPath(), new File(getProgressFilePath()).toPath(),
                    StandardCopyOption.REPLACE_EXISTING);

        } catch (NoSuchFileException e) {
            System.err.println("目标路径的父目录不存在: " + e.getMessage());
        } catch (IOException e) {
            System.err.println("其他错误: " + e.getMessage());
        }

    }

    //读取进度
    private synchronized void loadProgress() {
        final File file = new File(getProgressFilePath());
        if (file.exists()){
            readProgress(file);
        }else{
            File tempFile = new File(getProgressFilePath() + ".tmp");
            if (tempFile.exists()){
                readProgress(tempFile);
            }
        }

    }

    private void readProgress(File file) {
        try (BufferedReader reader = new BufferedReader(new FileReader(file))) {
            String line;
            while ((line = reader.readLine()) != null) {
                String[] parts = line.split(":");
                if (parts.length == 2) {
                    int tid = Integer.parseInt(parts[0]);
                    long bytes = Long.parseLong(parts[1]);
                    progressMap.put(tid, bytes);
//                    System.out.println(localFilePath + ":" + tid + "=" + bytes);
                    downloadedBytes.addAndGet(bytes);
                }
            }
        } catch (IOException e) {
            System.out.println(file.getName() + "读取失败");
            e.printStackTrace();
        }
    }

    //获取进度文件名称
    private String getProgressFilePath() {
        // 使用文件URL的哈希值作为进度文件名的一部分，确保唯一性
        int urlHash = fileUrl.hashCode();
        return localFilePath + "_" + urlHash + ".progress";
    }
}



